#include <common>
#include <packing>

varying vec2 vUv;
varying vec2 vUv2;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec3 vTangent;
varying vec3 vBitangent;

uniform vec3 diffuse;
uniform vec3 emissive;
uniform float roughness;
uniform float metalness;
uniform float brightness_specular;
uniform float opacity;


uniform sampler2D map;

uniform sampler2D normalMap;
uniform vec2 normalScale;

uniform sampler2D sssLUT;

#include <lights_pars_begin>
#include <bsdfs>

/* Define RenderEquations (RE) */


    struct PhysicalMaterial {

        vec3 diffuseColor;
        float roughness;
        vec3 specularColor;
        float specularF90;

    };

    // temporary
    vec3 clearcoatSpecular = vec3( 0.0 );

    float curve = 1.0;

    // Analytical approximation of the DFG LUT, one half of the
    // split-sum approximation used in indirect specular lighting.
    // via 'environmentBRDF' from "Physically Based Shading on Mobile"
    // https://www.unrealengine.com/blog/physically-based-shading-on-mobile
    vec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {

        float dotNV = saturate( dot( normal, viewDir ) );

        const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );

        const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );

        vec4 r = roughness * c0 + c1;

        float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;

        vec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;

        return fab;

    }

    // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
    // Approximates multiscattering in order to preserve energy.
    // http://www.jcgt.org/published/0008/01/03/
    void computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {

        vec2 fab = DFGApprox( normal, viewDir, roughness );

        vec3 FssEss = specularColor * fab.x + specularF90 * fab.y;

        float Ess = fab.x + fab.y;
        float Ems = 1.0 - Ess;

        vec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619; // 1/21
        vec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );

        singleScatter += FssEss;
        multiScatter += Fms * Ems;

    }


    float PHBeckmann( float NdotH , float roughness ){
        float alpha = acos( NdotH );
        float ta = tan(alpha);
        float m = roughness * roughness ;
        float val = 1.0 / ( m * pow(NdotH,4.0) ) * exp( -(ta*ta)/ m );
        return val;
    }

    float fresnelReflectance( vec3 H, vec3 V, float F0 )
    {
        float base = 1.0 - dot( V, H );
        float exponential = pow( base, 5.0 );
        return exponential + F0 * ( 1.0 - exponential );
    }

    void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {

        float dotNL = dot( geometry.normal, directLight.direction );

        // Kelemen/Szirmay-Kalos Specular
        // https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-14-advanced-techniques-realistic-real-time-skin
        if(dotNL > 0.0){
            vec3 h =  directLight.direction + geometry.viewDir ; // Unnormalized half-way vector 
            vec3 H = normalize( h ); 
            float dotNH = dot( geometry.normal , H );
            float PH = PHBeckmann( dotNH , roughness );
            float F = fresnelReflectance( H,geometry.viewDir,0.028 );
            float frSpec = max( PH * F / dot(h,h) , 0.0 );
            reflectedLight.directSpecular +=  dotNL * brightness_specular * frSpec * directLight.color ;
        }

        dotNL = saturate( dotNL );

        vec3 irradiance = dotNL * directLight.color;

        // reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );

        reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );

        //Subsurface Scattering
        float wrappedDotNL = (dot(directLight.direction, geometry.normal) * 0.5 + 0.5);
        vec4 scatteringColor = texture2D(sssLUT, vec2(wrappedDotNL, 1.0 / curve  ));


        reflectedLight.directDiffuse += (1.0 - wrappedDotNL) * directLight.color * material.diffuseColor * scatteringColor.rgb * 0.25;

    }

    void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {

        reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );

    }

    void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {

        // Both indirect specular and indirect diffuse light accumulate here

        vec3 singleScattering = vec3( 0.0 );
        vec3 multiScattering = vec3( 0.0 );
        vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;

        computeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );

        vec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );

        reflectedLight.indirectSpecular += radiance * singleScattering;
        reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;

        reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;

    }

    #define RE_Direct				RE_Direct_Physical
    #define RE_IndirectDiffuse		RE_IndirectDiffuse_Physical
    #define RE_IndirectSpecular		RE_IndirectSpecular_Physical

    // ref: https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
    float computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {

        return saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );

    }
    

/* Define RenderEquations (RE) */



void main(void){

    vec4 diffuseColor = vec4( diffuse, opacity );
    ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
	vec3 totalEmissiveRadiance = emissive;

    // map_fragment
    vec4 texelColor = texture2D( map, vUv );

	diffuseColor *= texelColor;


    // sss
    float CurveFactor = 1.0;
    curve = length( fwidth( vNormal ) ) / (length( fwidth( vViewPosition  ) ) * CurveFactor );

    // metalnessmap_fragment
    float metalnessFactor = metalness;

    
    // roughnessmap_fragment
    float roughnessFactor = roughness;

    /* normal */

        // normal_fragment_begin
        vec3 normal = normalize( vNormal );
        vec3 tangent = normalize( vTangent );
        vec3 bitangent = normalize( vBitangent );
        mat3 vTBN = mat3( tangent, bitangent, normal );

        vec3 geometryNormal = normal;

        // normal_fragment_maps
        vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
        mapN.xy *= normalScale;
        normal = normalize( vTBN * mapN );

    
    /* normal */

	/* accumulation */
	
        // lights_physical_fragment
        PhysicalMaterial material;
        material.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );


        vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );
        float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );

        material.roughness = max( roughnessFactor, 0.0525 );// 0.0525 corresponds to the base mip of a 256 cubemap.
        material.roughness += geometryRoughness;
        material.roughness = min( material.roughness, 1.0 );

        material.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );
        material.specularF90 = 1.0;

        
        // lights_fragment_begin
        #include <lights_fragment_begin>

        // lights_fragment_maps
        #include <lights_fragment_maps>
        
        // lights_fragment_end
        #include <lights_fragment_end>

    /* accumulation */



    // output_fragment
    vec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;
	vec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;
    vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;
    diffuseColor.a = 1.0;
    gl_FragColor = vec4( outgoingLight, diffuseColor.a );

}